Skip to content

feat(connector): add Ilixium routing via unified connector service - #13708

Merged
shuklatushar226 merged 10 commits into
mainfrom
feat/grace-ilixium
Aug 19, 2026
Merged

feat(connector): add Ilixium routing via unified connector service#13708
shuklatushar226 merged 10 commits into
mainfrom
feat/grace-ilixium

Conversation

@shuklatushar226

@shuklatushar226 shuklatushar226 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Type of Change

  • New feature

Description

Registers Ilixium as a Hyperswitch connector so that card payments can be routed to the Unified Connector Service (UCS), where the actual Ilixium Direct API integration lives. There is no in-repo Ilixium HTTP integration — Ilixium is added to ucs_only_connectors, so every attempt takes the UCS gateway path.

Linked UCS PR: juspay/hyperswitch-prism#2125

What this does

  • Adds Ilixium to common_enums::Connector, euclid::enums::{Connector, RoutableConnectors} and the Connector -> RoutableConnectors mappings, so Connector::from_str("ilixium") (used by should_call_unified_connector_service) resolves and the connector is routable.

  • Adds a UCS-only connector stub at crates/hyperswitch_connectors/src/connectors/ilixium.rs (connector-template shape). It exists only so ConnectorData::get_connector_by_name / feature-matrix resolution succeed before the UCS/Direct decision is made; its only meaningful logic is validating the SignatureKey auth shape.

  • Adds ConnectorSpecificConfig::Ilixium in crates/router/src/core/unified_connector_service/connector_config.rs. Without this, build_connector_config_header returns an error and the UCS call fails. The x-connector-config header now carries:

    MCA field Ilixium meaning Where UCS uses it
    api_key Digest Calculation Password input to x-merchant-digest (never transmitted)
    key1 MerchantId request body merchant.merchantId
    api_secret AccountId request body merchant.accountId
  • Adds ilixium to ucs_only_connectors in config/development.toml, config/config.example.toml, config/deployments/env_specific.toml and config/deployments/integration_test.toml (deliberately not sandbox/production — no credentials yet).

  • Adds ilixium.base_url = "https://prprocessing.ilixium.com/platform/ili" to the config files, control-center connector config entries (SignatureKey, credit/debit card networks) and a cards required-fields entry (card fields + email + billing country, which the UCS connector mandates).

  • Regenerates the ilixium enum value into the OpenAPI specs.

Additional fix: connector_order_id on UCS refund / refund-sync requests

Refunds routed through UCS never carried connector_order_id — both
PaymentServiceRefundRequest and RefundServiceGetRequest hardcoded it to None in
crates/router/src/core/unified_connector_service/transformers.rs. Per the proto, that field is
"the connector-side identifier for the original payment that this refund targets".

This is a real, connector-agnostic gap, and it makes refunds impossible for connectors that bind a
refund to the merchant-side reference of the original payment rather than to a gateway-generated
id. Ilixium is one: POST /direct/refund and the POST /history/operations filter used by RSync
are both keyed on the original authorisation's transaction.merchantRef, and Ilixium publishes no
gateway-id lookup. UCS's connector_transaction_id holds Ilixium's gatewayRef (a dashed UUID),
which is not a merchantRef and cannot be converted into one, so the connector refuses locally
with Missing required field: connector_order_id (surfaced as IR_06) rather than sending a
reference that points at nothing.

The fix follows the precedent already established by the Capture and Void paths, which send the
original attempt's stored connector_request_reference_id as merchant_capture_id /
merchant_void_id:

  • Adds RefundsData::payment_connector_request_reference_id, populated in both v1 and v2
    construct_refund_router_data from payment_attempt.connector_request_reference_id (the same
    value the Authorize went out with; core::utils::get_connector_request_reference_id
    short-circuits to the stored value so every leg of a payment reuses it).
  • Sends it as connector_order_id on both the UCS Refund and RSync requests.
  • Relay refunds have no locally recorded payment attempt, so they pass None — unchanged
    behaviour.

This only populates a field that was previously always None, so no other connector's behaviour
changes: connector_order_id is read on the refund/refund-sync path by Ilixium alone (the other
UCS connectors that use it — Razorpay, Easebuzz, PayPal, Cashfree, Airwallex, Affirm, Flywire —
read it on Authorize/CreateOrder, which this PR does not touch).

Scope

Card one-time payments (3DS + no-3DS) plus the Capture / Void / Refund / RSync legs that UCS already implements. No mandates or wallets.

Motivation and Context

Ilixium was implemented in the connector-service (UCS) repo. Hyperswitch could not route to it because the connector name did not exist in the Connector enum and there was no ConnectorSpecificConfig variant to build the x-connector-config header from.

How did you test it?

  • cargo check --workspace with the full v1 feature set — clean
  • cargo check --workspace with the full v2 feature set — clean
  • cargo clippy --workspace --all-targets (v1 features) — clean
  • cargo +nightly fmt --all -- --check — clean
  • Verified ConnectorConfig::get_connector_config(Connector::Ilixium) loads the new SignatureKey dashboard config

End-to-end verified against a mock Ilixium server (no Ilixium sandbox credentials exist yet),
driving Hyperswitch -> UCS -> mock and asserting on the bytes the mock actually received:

  • Authorize (manual capture) -> Capture -> POST /refunds -> refund sync, all succeeded.
  • The mock received POST /platform/ili/direct/refund, and its transaction.merchantRef is
    byte-identical to the merchantRef sent on the original POST /platform/ili/direct/auth — which
    is the whole point of the fix. Before the fix the same flow failed with
    IR_06 Missing required field: connector_order_id.
  • Refund sync reached POST /platform/ili/history/operations and the refund resolved to
    succeeded.
  • x-merchant-digest recomputes correctly from the raw received bytes on every request.
  • Re-ran a no-3DS payment afterwards — still succeeded, no regression.

End-to-end testing is planned against a mock Ilixium server, because no Ilixium sandbox credentials exist yet. Manual verification recipe:

  1. Enable UCS: insert ucs_enabled = "true" into the configs table, and point [grpc_client.unified_connector_service] base_url at a running connector-service.
  2. Create the MCA:
POST /account/{merchant_id}/connectors
{
  "connector_type": "payment_processor",
  "connector_name": "ilixium",
  "connector_account_details": {
    "auth_type": "SignatureKey",
    "api_key":   "<Digest Calculation Password>",
    "key1":      "<MerchantId>",
    "api_secret":"<AccountId>"
  },
  "payment_methods_enabled": [
    { "payment_method": "card",
      "payment_method_types": [
        { "payment_method_type": "credit", "card_networks": ["Visa","Mastercard"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": false, "installment_payment_enabled": false },
        { "payment_method_type": "debit",  "card_networks": ["Visa","Mastercard"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": false, "installment_payment_enabled": false }
      ] }
  ]
}
  1. Create a payment with "connector": ["ilixium"], card payment method data, email, billing.address.country, and — for 3DS — authentication_type: "three_ds", a return_url and a full browser_info block.

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code

🤖 Generated with Claude Code

Registers Ilixium as a Hyperswitch connector so card payments can be
routed to the Unified Connector Service (UCS), where the actual Ilixium
Direct API integration lives.

- Add `Ilixium` to `common_enums::Connector`, `euclid` `Connector` /
  `RoutableConnectors` and the connector -> routable-connector mappings
- Add a UCS-only connector stub (`hyperswitch_connectors::connectors::Ilixium`)
  so `ConnectorData`/feature-matrix resolution succeeds; the stub only
  validates the `SignatureKey` auth shape
- Add `ConnectorSpecificConfig::Ilixium` so the `x-connector-config`
  header sent to UCS carries the three credentials:
  api_key = Digest Calculation Password, key1 = MerchantId,
  api_secret = AccountId
- Add `ilixium` to `ucs_only_connectors` in development / example /
  env_specific / integration_test configs so it always takes the UCS path
- Add `ilixium.base_url` and dashboard connector config entries

Scope is card one-time Authorize (3DS + no-3DS) only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226
shuklatushar226 requested review from a team as code owners August 13, 2026 18:00
@semanticdiff-com

semanticdiff-com Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review changes with  SemanticDiff

Changed Files
File Status
  crates/router/src/connector.rs  99% smaller
  crates/hyperswitch_connectors/src/connectors.rs  93% smaller
  crates/router/src/core/unified_connector_service/transformers.rs  34% smaller
  crates/hyperswitch_connectors/src/default_implementations.rs  32% smaller
  crates/common_enums/src/connector_enums.rs  26% smaller
  crates/hyperswitch_connectors/src/default_implementations_v2.rs  17% smaller
  api-reference/v1/openapi_spec_v1.json  0% smaller
  api-reference/v2/openapi_spec_v2.json  0% smaller
  config/config.example.toml Unsupported file format
  config/deployments/env_specific.toml Unsupported file format
  config/deployments/integration_test.toml Unsupported file format
  config/deployments/production.toml Unsupported file format
  config/deployments/sandbox.toml Unsupported file format
  config/development.toml Unsupported file format
  config/docker_compose.toml Unsupported file format
  crates/connector_configs/src/connector.rs  0% smaller
  crates/connector_configs/toml/development.toml Unsupported file format
  crates/connector_configs/toml/production.toml Unsupported file format
  crates/connector_configs/toml/sandbox.toml Unsupported file format
  crates/euclid/src/enums.rs  0% smaller
  crates/hyperswitch_connectors/src/connectors/ilixium.rs  0% smaller
  crates/hyperswitch_connectors/src/connectors/ilixium/transformers.rs  0% smaller
  crates/hyperswitch_domain_models/src/connector_endpoints.rs  0% smaller
  crates/hyperswitch_domain_models/src/router_request_types.rs  0% smaller
  crates/payment_methods/src/configs/payment_connector_required_fields.rs  0% smaller
  crates/router/src/core/connector_validation.rs  0% smaller
  crates/router/src/core/relay/utils.rs  0% smaller
  crates/router/src/core/unified_connector_service/connector_config.rs  0% smaller
  crates/router/src/core/utils.rs  0% smaller
  crates/router/src/types/api/connector_mapping.rs  0% smaller
  crates/router/src/types/api/feature_matrix.rs  0% smaller
  crates/router/src/types/connector_transformers.rs  0% smaller
  crates/router/tests/connectors/utils.rs  0% smaller
  loadtest/config/development.toml Unsupported file format

@hyperswitch-bot hyperswitch-bot Bot added the M-api-contract-changes Metadata: This PR involves API contract changes label Aug 13, 2026
…ector_order_id via UCS

Refunds routed through the Unified Connector Service never carried
`connector_order_id`: both `PaymentServiceRefundRequest` (Refund) and
`RefundServiceGetRequest` (RSync) hardcoded it to `None`. The proto defines
that field as "the connector-side identifier for the original payment that this
refund targets", and the only site that ever populated it was CompleteAuthorize.

Connectors that bind a refund to the merchant-side reference of the original
payment — rather than to a gateway-generated id — therefore cannot refund at
all. Ilixium is one: `POST /direct/refund`, and the `POST /history/operations`
filter that RSync uses, are both keyed on the original authorisation's
`transaction.merchantRef`, and no gateway-id lookup is published. UCS's
`connector_transaction_id` holds Ilixium's `gatewayRef` (a dashed UUID), which
is not a `merchantRef` and cannot be converted into one, so the connector
refuses locally with `Missing required field: connector_order_id`, surfaced to
the merchant as `IR_06`.

Follow the precedent already set by the Capture and Void paths, which send the
original attempt's stored `connector_request_reference_id` as
`merchant_capture_id` / `merchant_void_id`:

- add `RefundsData::payment_connector_request_reference_id`, populated in both
  the v1 and v2 `construct_refund_router_data` from
  `payment_attempt.connector_request_reference_id` — the same value the
  Authorize went out with, since `get_connector_request_reference_id`
  short-circuits to the stored value for every subsequent leg of a payment;
- send it as `connector_order_id` on the UCS Refund and RSync requests;
- relay refunds have no locally recorded payment attempt, so they pass `None`,
  which is what they sent before.

This only populates a field that was previously always `None`, so no other
connector's behaviour changes: on the refund and refund-sync path
`connector_order_id` is read by Ilixium alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
shuklatushar226 and others added 3 commits August 16, 2026 01:07
Ilixium performs 3-D Secure inside its own authorisation call rather than through
standalone authentication endpoints, so the two legs are POST /direct/auth and
POST /direct/threedcomplete. This opts the connector into `pre_authentication_step`
for 3DS card payments; the second leg continues to run as Authorize via
CompleteAuthorize, because /direct/threedcomplete is what settles the payment.

Note /direct/auth is terminal in two of its three outcomes — Ilixium decides at
response time whether to challenge, so a not-enrolled or frictionless card is
charged by that single call. No `Connector::Ilixium` arm is added to
`pre_authentication_step`'s `should_continue` match on purpose: the `_ => false`
default is exactly right here, and returning true would re-send /direct/auth and
earn response code 102, "Duplicate Merchant Ref".

Also fills two fields on the PreAuthenticate request that the Authorize builder
already sends, so a connector whose PreAuthenticate leg carries a full
authorisation sees the same identity and reference:

  * `customer.id` from the router data's customer_id (was hardcoded None)
  * `merchant_transaction_id` from connector_request_reference_id (was None);
    UCS resolves this leg's reference from merchant_order_id, so this is for
    connectors that read merchant_transaction_id directly, e.g. Kount's DDC
    sessionId.

Verified end to end against a mock Ilixium (curl -> hyperswitch -> UCS -> mock):
non-3DS, frictionless, not-enrolled, challenge and decline all behave, and the
challenge completes to `charged` with no duplicate /direct/auth.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226 shuklatushar226 self-assigned this Aug 17, 2026
shuklatushar226 and others added 3 commits August 18, 2026 00:16
Both were added in 2783e37 as "parity with the Authorize builder", but
neither reaches anything on the Ilixium path:

* `customer.id` — UCS's PreAuthenticate conversion resolves PaymentFlowData's
  customer_id from `customer.connector_customer_id`, not `customer.id`, and its
  PaymentsPreAuthenticateData conversion reads only `customer.email`. The field
  being populated here is not the field being read there. Ilixium also has no
  CreateConnectorCustomer flow, so connector_customer is always None for it.
* `merchant_transaction_id` — UCS resolves this leg's
  connector_request_reference_id from `merchant_order_id`, which is already sent.
  Ilixium never reads PaymentsPreAuthenticateData::merchant_transaction_id.

Faithfully mirroring Authorize is not a good enough reason to change a request
builder shared by every connector. `is_pre_authentication_flow_required` is the
actual Ilixium change and is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ilixium's 3DS challenge could never complete. The ACS post-back to
/redirect/complete/ilixium returned 400 and the attempt stayed stuck at
`authentication_pending`, with UCS answering "Missing required field:
payment_method".

The cause is that `should_store_payment_method_data_in_vault` returns false for
any connector absent from `temp_locker_enable_config`. With no entry, no
payment_token is created at confirm, so `make_pm_data` has nothing to re-fetch
on the CompleteAuthorize leg and hyperswitch sends no payment_method at all.

This is not specific to Ilixium — it is how the fleet handles redirect 3DS.
Every card connector that does it is already listed: nuvei, shift4, bluesnap,
bankofamerica, cybersource, barclaycard, nmi, payme, paybox, nexixpay, redsys,
worldpayxml. Ilixium was simply missing.

Added to all six config files, matching each one's existing style (inline table
in development/docker_compose/config.example, dotted key in the deployments).

Verified end to end against a mock Ilixium: payment_token is now populated at
confirm, the ACS post-back returns 302, and the attempt settles to `charged` via
/direct/threedcomplete, with one /direct/auth and no duplicates.

This is what makes reverting the shared-enum `NoInstrumentAfterRedirect` change
possible — see the corresponding revert in the connector-service repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ilixium requires `customer.firstName` and `customer.surname` on every
authorisation — both appear in all four Direct API customer examples and have
their own validation codes (51, 52). The Optional Address feature, the only
relaxation the vendor documents, explicitly relaxes addressLine1, city and
postcode only; names are untouched.

Hyperswitch was asking the merchant for neither, so the payment reached UCS and
failed there with MissingRequiredField instead of being caught up front.

This matters more since the 3DS first leg moved to PreAuthenticate. On the
Authorize path the connector can fall back to splitting `customer_name` on the
first whitespace, which masks the gap. `PaymentsPreAuthenticateData` has no
`customer_name` field at all, so on a 3DS payment the billing address is the
only name source and there is nothing to fall back on.

Uses the same idiom as Deutschebank, which declares these alongside card fields
and email in the same non_mandate bucket. Preferred over the no-argument
BillingUserFirstName/BillingUserLastName pair that the adjacent Givepayments
entry uses, because those hardcode a "card_holder_name" display name — wrong
here, since Ilixium wants the customer's name, not the name on the card.

Verified against a running stack: GET /account/payment_methods now advertises
billing.address.first_name and billing.address.last_name for Ilixium cards,
alongside the existing country and email requirements.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolves the collisions from Citigate landing on main while Ilixium was in
review. Both connectors are kept everywhere:

* ucs_only_connectors (4 config files) — took main's list and appended ilixium,
  so any other edits main made to that list survive rather than being replaced
  by our older copy.
* crates/connector_configs/toml/{development,production,sandbox}.toml — both
  [citigate] and [ilixium] tables retained; each side's hunk was a complete
  table appended at EOF.
* crates/router/src/connector.rs — the two sides were byte-identical apart from
  each side's own connector, so main's list was taken and ilixium inserted in
  alphabetical position after iatapay.

Re-wrapped with nightly rustfmt: inserting into that `pub use` list left a
126-char line that stable rustfmt cannot re-flow (the repo's
imports_granularity/group_imports settings are nightly-only), which would have
failed `cargo +nightly fmt --all --check` in CI.

Verified both sides survived — citigate and ilixium occurrence counts in every
resolved file match main and the branch respectively — and cargo check is clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226
shuklatushar226 requested a review from a team as a code owner August 18, 2026 09:30
@@ -0,0 +1,2 @@
request-property-one-of-removed warn
response-property-one-of-added warn

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this change needed?

Swept into the main->feat/grace-ilixium merge from an untracked working-tree
file; present in neither parent. The workflow dropped --severity-levels in
bc75604, so nothing reads it, and this branch's only spec delta is an enum
value already covered by .oasdiff-warn-ignore.yaml.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226
shuklatushar226 removed the request for review from a team August 18, 2026 23:44
@shuklatushar226
shuklatushar226 added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main with commit ada4a1e Aug 19, 2026
50 of 55 checks passed
@shuklatushar226
shuklatushar226 deleted the feat/grace-ilixium branch August 19, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

M-api-contract-changes Metadata: This PR involves API contract changes S-test-ready Status: This PR is ready for cypress-tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Integration][P0] Illexium — cards one-time payments

7 participants